package io.vertx.kue.queue;

import io.vertx.codegen.annotations.DataObject;
import io.vertx.codegen.annotations.Fluent;
import io.vertx.core.Vertx;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.redis.RedisClient;
import java.util.UUID;

/**
 * Vert.x Kue
 * Job domain class.
 *
 * @author Eric Zhao
 */
@DataObject(generateConverter = true)
public class Job {

  // TODO: refactor the job class.

  protected static Logger logger = LoggerFactory.getLogger(Job.class);

  protected static Vertx vertx;
  protected static RedisClient client;
  protected static EventBus eventBus;

  public static void setVertx(Vertx v, RedisClient redisClient) {
    vertx = v;
    client = redisClient;
    eventBus = vertx.eventBus();
  }

  // job properties

  private final String address_id;
  private long id = -1;
  private String zid;
  private String type;
  private JsonObject data;
  private Priority priority = Priority.NORMAL;
  private JobState state = JobState.INACTIVE;
  private long delay = 0;
  private int max_attempts = 1;
  private boolean removeOnComplete = false;
  private int ttl = 0;
  private JsonObject backoff;

  private int attempts = 0;
  private int progress = 0;
  private JsonObject result;

  // job metrics
  private long created_at;
  private long promote_at;
  private long updated_at;
  private long failed_at;
  private long started_at;
  private long duration;

  public Job() {
    this.address_id = UUID.randomUUID().toString();
    _checkStatic();
  }

  public Job(JsonObject json) { // TODO: optimize this!
    JobConverter.fromJson(json, this);
    this.address_id = json.getString("address_id");
    // generated converter cannot handle this
    if (this.data == null) {
      this.data = new JsonObject(json.getString("data"));
      if (json.getValue("backoff") != null) {
        this.backoff = new JsonObject(json.getString("backoff"));
      }
      this.progress = Integer.parseInt(json.getString("progress"));
      this.attempts = Integer.parseInt(json.getString("attempts"));
      this.max_attempts = Integer.parseInt(json.getString("max_attempts"));
      this.created_at = Long.parseLong(json.getString("created_at"));
      this.updated_at = Long.parseLong(json.getString("updated_at"));
      this.started_at = Long.parseLong(json.getString("started_at"));
      this.promote_at = Long.parseLong(json.getString("promote_at"));
      this.delay = Long.parseLong(json.getString("delay"));
      this.duration = Long.parseLong(json.getString("duration"));
    }
    if (this.id < 0) {
      if ((json.getValue("id")) instanceof CharSequence)
        this.setId(Long.parseLong(json.getString("id")));
    }
    _checkStatic();
  }

  public Job(Job other) {
    this.id = other.id;
    this.zid = other.zid;
    this.address_id = other.address_id;
    this.type = other.type;
    this.data = other.data == null ? null : other.data.copy();
    this.priority = other.priority;
    this.state = other.state;
    this.delay = other.delay;
    // job metrics
    this.created_at = other.created_at;
    this.promote_at = other.promote_at;
    this.updated_at = other.updated_at;
    this.failed_at = other.failed_at;
    this.started_at = other.started_at;
    this.duration = other.duration;
    this.attempts = other.attempts;
    this.max_attempts = other.max_attempts;
    this.removeOnComplete = other.removeOnComplete;
    _checkStatic();
  }

  public Job(String type, JsonObject data) {
    this.type = type;
    this.data = data;
    this.address_id = UUID.randomUUID().toString();
    _checkStatic();
  }

  public JsonObject toJson() {
    JsonObject json = new JsonObject();
    JobConverter.toJson(this, json);
    return json;
  }

  private void _checkStatic() {
    if (vertx == null) {
      logger.warn("static Vertx instance in Job class is not initialized!");
    }
  }

  /**
   * Set job priority.
   *
   * @param level job priority level
   */
  @Fluent
  public Job priority(Priority level) {
    if (level != null)
      this.priority = level;
    return this;
  }






  // getter and setter

  public long getId() {
    return id;
  }

  public Job setId(long id) {
    this.id = id;
    return this;
  }

  public JsonObject getData() {
    return data;
  }

  public Job setData(JsonObject data) {
    this.data = data;
    return this;
  }

  public String getType() {
    return type;
  }

  public Job setType(String type) {
    this.type = type;
    return this;
  }

  public Priority getPriority() {
    return priority;
  }

  public Job setPriority(Priority priority) {
    this.priority = priority;
    return this;
  }

  public JsonObject getResult() {
    return result;
  }

  public Job setResult(JsonObject result) {
    this.result = result;
    return this;
  }

  public int getProgress() {
    return progress;
  }

  public Job setProgress(int progress) {
    this.progress = progress;
    return this;
  }

  public long getDelay() {
    return delay;
  }

  public Job setDelay(long delay) {
    if (delay > 0) {
      this.delay = delay;
    }
    return this;
  }

  public JobState getState() {
    return state;
  }

  public String stateName() {
    return state.name();
  }

  public Job setState(JobState state) {
    this.state = state;
    return this;
  }

  public String getZid() {
    return zid;
  }

  public Job setZid(String zid) {
    this.zid = zid;
    return this;
  }

  public boolean hasAttempts() {
    return this.max_attempts - this.attempts > 0;
  }

  public int getAttempts() {
    return attempts;
  }

  public Job setAttempts(int attempts) {
    this.attempts = attempts;
    return this;
  }

  public long getCreated_at() {
    return created_at;
  }

  public Job setCreated_at(long created_at) {
    this.created_at = created_at;
    return this;
  }

  public long getPromote_at() {
    return promote_at;
  }

  public Job setPromote_at(long promote_at) {
    this.promote_at = promote_at;
    return this;
  }

  public long getUpdated_at() {
    return updated_at;
  }

  public Job setUpdated_at(long updated_at) {
    this.updated_at = updated_at;
    return this;
  }

  public long getFailed_at() {
    return failed_at;
  }

  public Job setFailed_at(long failed_at) {
    this.failed_at = failed_at;
    return this;
  }

  public long getStarted_at() {
    return started_at;
  }

  public Job setStarted_at(long started_at) {
    this.started_at = started_at;
    return this;
  }

  public long getDuration() {
    return duration;
  }

  public Job setDuration(long duration) {
    this.duration = duration;
    return this;
  }

  public int getMax_attempts() {
    return max_attempts;
  }

  public Job setMax_attempts(int max_attempts) {
    this.max_attempts = max_attempts;
    return this;
  }

  public String getAddress_id() {
    return address_id;
  }

  public boolean isRemoveOnComplete() {
    return removeOnComplete;
  }

  public Job setRemoveOnComplete(boolean removeOnComplete) {
    this.removeOnComplete = removeOnComplete;
    return this;
  }

  public JsonObject getBackoff() {
    return backoff;
  }

  public Job setBackoff(JsonObject backoff) {
    this.backoff = backoff;
    return this;
  }

  public int getTtl() {
    return ttl;
  }

  public Job setTtl(int ttl) {
    this.ttl = ttl;
    return this;
  }


  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Job job = (Job) o;

    if (id != job.id) return false;
    if (!address_id.equals(job.address_id)) return false;
    return type.equals(job.type);

  }

  @Override
  public int hashCode() {
    int result = address_id.hashCode();
    result = 31 * result + (int) (id ^ (id >>> 32));
    result = 31 * result + type.hashCode();
    return result;
  }

  @Override
  public String toString() {
    return this.toJson().encodePrettily();
  }
}
